using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SsLib
{
    /// <summary>
    /// SimObject serves as the implicit prefix class for all non-prefixed Sim# classes.
    /// </summary> 
    /// <remarks>
    /// SimObject provides basic facilities common to all Sim# classes such as:
    /// Coroutine support through the static LaunchCoroutine procedure and Detach method
    /// accessible for all Sim# clases.
    /// 
    /// Class block and INNER statement handling via Prolog and Epilog virtual methods.
    /// All object initialization should be placed in class construcor.
    /// All Class arguments should be promoted to fields and assigned in the constructor.
    /// 
    /// All class block statements before the optional INNER should be placed in the Prolog method.
    /// All class block statements after the INNER statement (if used) should be placed in the Epilog method.
    /// 
    /// Upon object creating Sim# will first call the object constructor for the class.
    /// Then Sim# will launch a Coroutine on another thread which in turn calls the Prolog virtual method and finally
    /// the Epilog method.
    /// 
    /// All Prolog methods should/must call the base.Prolog() method before executing any statements.
    /// An INNER statement, if used, will terminate the Prolog method.
    /// 
    /// After Prolog is executed the Coroutine will execute the Epilog virtual method.
    /// All Epilog methods should/must call the base.Epilog() method after executing the rest of the
    /// class block statements after INNER, but before terminating (returning).
    /// 
    /// This construct will efficiently implement Class blocks, Head and Tailing (through Inner) and
    /// Coroutines in Sim#.
    /// </remarks>
    public abstract class SimObject
    {
        /// <summary>
        /// Exeucution state enumeration for coroutines.
        /// </summary>
        internal enum BlockState
        {
            Attahed = 0,
            Detached,
            Resumed,
            Terminated
        }

        ///<summary>This field holds a reference to the global SYSIN inspect.</summary>
        protected BasicIO.InFile _sysin;

        ///<summary>This field holds a reference to the global SYSOUT inspect.</summary>
        protected BasicIO.PrintFile _sysout;

        ///<summary>This field holds a reference to the global BasicIO class, as an emulated inspect.</summary>
        protected BasicIO _basicio;


        ///<summary>Internal field holding the wait handle for this SimObject if it constitutes a coroutine.</summary>
        internal AutoResetEvent _waitHandle;
        ///<summary>Internal field holding the wait handle for the SimObject this instance is attached to, if it constitutes a coroutine.</summary>
        internal AutoResetEvent _attachedTo;

        /// <summary>Internal field holding the execution state for the current object.</summary>
        internal BlockState _executionState = BlockState.Attahed;

        ///<summary>Holds the coroutine object for the currently executing thread.</summary>
        ///<remarks>Marking this static field as ThreadStatic ensures that each thread has its own local version of this static field.</remarks>
        [ThreadStatic]
        internal static SimObject CurrentCoroutine;

        /// <summary>
        /// Constructor for SimObject objects.
        /// The constructor will assign references from BasicIO to these protected fields:
        /// _basicio - provides access to all public BasicIO and Environment procedures.
        /// _sysin - simulates inspect SYSIN and provides access to standard input file (InImage).
        /// _sysout - simulates inspet SYSOUT and provides access to standardd output file (PrinteFile)
        /// This efficiently provides Sim# with global access to BasicIO, Sysin and Sysout.
        /// </summary>
        public SimObject()
        {
            if (BasicIO._outer != null)
            {
                _basicio = BasicIO._outer;
                _sysin = BasicIO._outer.SysIn();
                _sysout = BasicIO._outer.SysOut();
            }
        }

        /// <summary>
        /// Prolog serves as the class block container for the class block statements.
        /// If Head and Tailing is needed, place all Head statements in Prolog method.
        /// and all Tail statement in Epilog method.
        /// </summary>
        /// <remarks>
        /// All implementers of Prolog should/must call base.Prolog() before executing any 
        /// class block statements.
        /// </remarks>
        public virtual void Prolog() { }

        /// <summary>
        /// Epilog serves as the class block container for class block Tailing statements.
        /// If Head and Tailing is needed, place all Head statements in Prolog method.
        /// and all Tail statement in Epilog method.
        /// </summary>
        /// <remarks>
        /// All implementers of Epilog should/must call base.Epilog() after executing the Tailing
        /// statements but before returning from the method.
        /// </remarks>        
        public virtual void Epilog() { }

        /// <summary>
        /// Detaches this SimObject and signals the owning coroutine to continue executing. This suspends the currently running thread and starts
        /// the thread for the signaled coroutine.
        /// </summary>
        public void Detach()
        {
            if (this._attachedTo == null) Environment.Error("Detach: Not an operating object.");
            if (SimObject.CurrentCoroutine._executionState == BlockState.Detached) Environment.Error("Detach: Already detached object.");

            SimObject.CurrentCoroutine._executionState = BlockState.Detached;
            AutoResetEvent.SignalAndWait(this._attachedTo, this._waitHandle);
        }


        /// <summary>
        /// Creates a new coroutine with its own thread and executes the coroutines main block (Prolog/Epilog).
        /// The currently running coroutine (thread) is suspended after launching the new coroutine.
        /// the currently running coroutine becomes the initial owning coroutine for the new coroutine. And will be signaled when the
        /// new coroutine exits its block and dies. This can be changed with the use of the Call procedure upon the new coroutine, where the 
        /// caller will be the new owner of the new coroutine, and will be signaled when the new coroutine exits.
        /// </summary>
        /// <remarks>
        /// The thread for the new coroutine will be marked as a background thread in order to not prevent the process from terminating.
        /// </remarks>
        /// <param name="coroutine">A newly created SimObject that should become a new coroutine.</param>
        public static void LaunchCoroutine(SimObject coroutine)
        {
            coroutine._waitHandle = new AutoResetEvent(false);
            coroutine._attachedTo = SimObject.CurrentCoroutine._waitHandle;

            Thread coThread = new Thread(new ParameterizedThreadStart(CoroutineStart));
            coThread.IsBackground = true;

            coThread.Start(coroutine);

            SimObject.CurrentCoroutine._waitHandle.WaitOne();            
        }


        /// <summary>
        /// Executes the new coroutine on a newly created thread.
        /// The main block of the coroutine is executed (Prolog/Epilog) and the owning coroutne is signaled just before this coroutine
        /// exits (and thread dies).
        /// </summary>
        /// <remarks>
        /// Sets the thread local CurrentWaitHandle to the coroutines WaitHandle.
        /// </remarks>
        /// <param name="coroutine">A newly created SimObject that should become a new coroutine.</param>
        private static void CoroutineStart(object coroutine)
        {
            SimObject co = (SimObject)coroutine;
            SimObject.CurrentCoroutine = co;

            co.Prolog();
            co.Epilog();
            co._executionState = BlockState.Terminated;
            EventWaitHandle ewh = co._attachedTo;
            co._attachedTo = null;
            co._waitHandle = null;
            ewh.Set();
        }
    }
}